#####################################################################
# File:         GenericObject.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the abstract GenericObject class,
#               the base class of all components used by the
#               Faulkner Knowledge Engine
#
# See Also:     GenericContainer, Component
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from ClassInfo import ClassInfo
from PropertyAccess import PropertyAccess
from PropertyReflection import PropertyReflection
from PropertyDefinition import PropertyDefinition
from PropertyFormats import pf_TEXT_SINGLELINE, pf_HTML_SINGLELINE
from Products.KnowledgeEngine.Messaging.MessageListener import MessageListener
from KnowledgeEngineCore import getClassInfo

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from Globals import DTMLFile, ImageFile
from AccessControl import ClassSecurityInfo
from Acquisition import Implicit
from Globals import Persistent
from AccessControl.Role import RoleManager
from App.Dialogs import MessageDialog
from OFS.SimpleItem import Item
from OFS.Traversable import Traversable
from OFS.CopySupport import CopyError
 
#####################################################################
## Python Library Imports
# (none)

#####################################################################
## GenericObjectInfo Class Definition
class GenericObjectInfo(ClassInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'GenericObject'


    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Generic Object"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.  
            
            This is a short string used for prefixing object identities
        """
        
        return "genericobject"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            ABSTRACT: Return the appropriate icon file for the class.
            All concrete classes must provide an icon!
        """
        
        return None
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: GenericObjects are permitted in GenericContainers
        return containerClassInfo.isInstanceOf('GenericContainer')

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: GenericObjects can't have Children (only GenericContainers can have children)
        return false
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ClassInfo.onConfigureProperties(self)
      
        #add the standard 'identity' property        
        self.addPropertyDefinition(PropertyDefinition(identity="identity", displayName="Identity", description="The Knowledge Engine unique identity for this object", propertyFormat=pf_TEXT_SINGLELINE, isWritePermitted=false, isForConstructor=false))

        #add the standard 'id' property        
        self.addPropertyDefinition(PropertyDefinition(identity="id", displayName="Zope ID", defaultValue=self.getClassName().lower(), description="Used to access the object via a URL within the object hierarchy (must not contain spaces)", propertyFormat=pf_TEXT_SINGLELINE, isForConstructor=true, isForPropertyEditor=false))

        #add the standard 'title' property        
        self.addPropertyDefinition(PropertyDefinition(identity="title", displayName="Title", defaultValue=self.getDisplayName(), description="The title for the object (displayed in headers)", propertyFormat=pf_HTML_SINGLELINE))
    
    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        

        #initialise the super-class events for reflection
        ClassInfo.onConfigureEvents(self)
        
    #---------------------------------------------------------------#

#####################################################################
## GenericObject Class Definition
class GenericObject(Item, Persistent, Implicit, RoleManager, Traversable, MessageListener, PropertyReflection, PropertyAccess):
    """
        ABSTRACT: Base class for all Faulkner Knowledge Engine components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = GenericObjectInfo()
    
    #---------------------------------------------------------------#

    # Define the default zope security information
    # (definitions can be extended, refined and overridden in subclasses)
    #TODO: security=ClassSecurityInfo()
    #TODO: security.declarePublic('index_html')

    #---------------------------------------------------------------#
    # Shared DTML Documents
    propertyEditor = DTMLFile('dtml/propertyEditor', globals())
    eventEditor = DTMLFile('dtml/eventEditor', globals())
    epozEditor = DTMLFile('dtml/epozEditor', globals())
    textEditor = DTMLFile('dtml/textEditor', globals())
        
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"    
        
        self.id = id
        self.identity = identity

        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def __setstate__(self, state):
        """
            Initialise the state of the Object
            (does backward compatibility)
        """

        #perform default persistent actions
        Persistent.__setstate__(self, state)
        
        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """        

        #clear the management tabs
        self.clearManagementTabs()

        #does the object have any events?
        if self.getClassInfo().hasEventDefinitions():
            self.addManagementTab('Events', 'eventEditor', None, true)
        
        #does the object have editable properties?
        if self.hasEditableProperties():
            self.addManagementTab('Properties', 'propertyEditor', None, true)


        #does the object support the Preview management tab?
        if self.isPreviewPermitted():        
            self.addManagementTab('Preview', 'preview_html', None, false)

    #---------------------------------------------------------------#

    def setModified(self):
        """
            Forces the object persistent state to be set as modified
        """
        
        self._p_changed = 1  #signal mutable object change for Zope

    #---------------------------------------------------------------#

    def clearManagementTabs(self):    
        """
            Clear the management tabs for the object
        """
        
        self.manage_options = ()

    #---------------------------------------------------------------#

    def existsManagementTab(self, label):
        """
            Return if the specified management tab exists
        """
                        
        result = false            
            
        for tabDictionary in self.manage_options:        
            result = result or (tabDictionary.has_key('label') and tabDictionary['label'] == label)
        
        return result
        
    #---------------------------------------------------------------#

    def addManagementTab(self, label, action, help = None, addToFront = false):
        """
            Add the specified management tab, replacing an existing 
            one if necessary
        """
                
        #does the tab actually exist?
        if self.existsManagementTab(label):
            #already exists... so update it            
            for tabDictionary in self.manage_options:        
            
                if tabDictionary.has_key('label') and tabDictionary['label'] == label:
                    tabDictionary['action'] = action
                    
                    if help <> None:
                        tabDictionary['help'] = help
                            
        else:
            #new tab... build the new tab definition
            tabDictionary = {'label':label, 'action':action}               
            if help <> None:
                tabDictionary['help'] = help
        
            if addToFront:
                self.manage_options = (tabDictionary,) + self.manage_options
            else:
                self.manage_options = self.manage_options + (tabDictionary,)        
                
    #---------------------------------------------------------------#

    def getIdentityType(self):
        "Returns the Identity Type for the object"        
        return self.identity_type

    #---------------------------------------------------------------#

    def getIdentity(self):
        "Returns the unique identity of the object"        
        return self.identity

    #---------------------------------------------------------------#

    def getTitle(self):
        "Returns the Title of the object"        
        return self.title

    #---------------------------------------------------------------#

    def setTitle(self, title):
        "Set the Title of the object"        
        self.title = title

    #---------------------------------------------------------------#

    def getTitleOrId(self):
        "Returns the Title (if the object has one), otherwise returns the Zope Id"        
        
        if len(self.getTitle()) > 0:
            return self.getTitle()
        else:
            return self.getId()

    #---------------------------------------------------------------#

    def hasTitle(self):
        "Returns if the object has a title"        
        return len(self.getTitle()) > 0

    #---------------------------------------------------------------#

    def isPublishable(self):
        "Returns if the object is publishable"        
        
        #TODO: check security here for current session
        return true

    #---------------------------------------------------------------#

    def getParent(self):
        "Return the parent container that this object is within"

        #return the parent of the inner object (all outer wrappers removed)
        return self.aq_inner.aq_parent

    #---------------------------------------------------------------#

    def getAbsolutePath(self):
        "Return the path to the object in the environment"
        
        #get that absolute path of the parent
        lPath = self.getParent().getAbsolutePath() + "/" + self.getId()
        return lPath

    #---------------------------------------------------------------#

    def getParentByInstanceOf(self, className):
        """
            Return the most immediate ancestor that implements
            the specified interface. Returns None if not found.
        """

        lParent = self.getParent()
        
        while not lParent.isInstanceOf("Environment") and not lParent.isInstanceOf(className):
            lParent = lParent.getParent()
        
        if lParent.isInstanceOf(className):
            return lParent 
        else:
            return None

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the 
            identity of this object is changed.
            
            Called By GenericObject.onAfterClone
        """

        pass

        ###print "DEBUG: GenericObject.onBeforeIdentityChange: Completed"

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the 
            identity of this object has changed.
            
            The identityMappings dictionary holds 
            mappings from oldIdentities -> newIdentities
            (including this object)
            
            Called By GenericObject.manage_afterClone
        """

        pass

        ###print "DEBUG: GenericObject.onAfterIdentityChange: Completed"
        
    #---------------------------------------------------------------#

    def alterIdentity(self):
        """
            Generates and changes the identity of the object
            CALLED BY: onAfterClone to ensure that pasted objects
            have different identities
        """
        
        #generate a new identity for the object     
        self.identity = generateIdentity(self.identity_type)        
        
    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = {}):
        "Handle Knowledge Engine specific actions after this object has been cloned"

        #handle before identity change
        self.onBeforeIdentityChange()

        #remember the old identity
        oldIdentity = self.identity

        #generate a new identity for the object     
        self.alterIdentity()
        
        #has the identity changed? (alterIdentity may not change the identity eg: like events don't)
        if oldIdentity != self.identity:
            #update the identityMappings with the old -> new identity
            identityMappings.update({oldIdentity:self.identity})
        
        ###print "DEBUG: GenericObject.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this object has been added to a container
        """

        pass
        
        ###print "DEBUG: GenericObject.onAfterAdd: Completed"
        
    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        "Handle Knowledge Engine specific actions before this object is deleted"

        pass

        ###print "DEBUG: GenericObject.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def resolveIdentities(self, identityMappings = {}):
        """
            Attempt to resolve all identity references 
            for this object and its decendants using the
            provided identityMappings dictionary
        """
        
        self.onAfterIdentityChange(identityMappings)        

    #---------------------------------------------------------------#

    def manage_afterClone(self, item):
        "FINAL: Override the standard Zope afterClone management"

        #does the item (root of the clone) have an identities mapping?
        if not hasattr(item, "_identityMappings"):
            item._identityMappings = {}
        
        #perform the clone operations            
        self.onAfterClone(item._identityMappings)        

        #resolve the old identities from the root of the clone (if this is the root) down
        if self == item or self.isInstanceOf("Environment"):            
            self.resolveIdentities(item._identityMappings)            
            
            #tidy up (if this is the last call)
            if self == item:
                #remove the temporary identity mappings attribute
                delattr(item, "_identityMappings")
                
                #as it is a clone, we need to adjust the position of the component
                #so it is last in the container
                self.setAsLast()

    #---------------------------------------------------------------#

    def manage_afterAdd(self, item, container):
        "FINAL: Override the standard Zope afterAdd management"
        
        self.onAfterAdd()
            
    #---------------------------------------------------------------#

    def manage_beforeDelete(self, item, container):
        "FINAL: Override the standard Zope beforeDelete management"
        
        self.onBeforeDelete()
        
    #---------------------------------------------------------------#

    def getClassInfo(self):
        """
            FINAL: All decendants of this class are expected to
            provide a ClassInfo instance
        """
        
        return self._CLASSINFO
        
    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            CONCRETE: Return if the object is a sub-class of
            the specified class name.

            NOTE: we do this to avoid Zope instanceof problems,
            including resolving cyclic references/imports
        """

        return self.getClassInfo().isInstanceOf(className)

    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the component is allowed to be previewed.
            
            By default all components can be previewed, some however
            should not be previewed. eg: Options whose display is 
            dependant on their parents.
        """
        
        return false

    #---------------------------------------------------------------#

    def getEnvironment(self):
        """
            Return the KnowledgeEngine Environment in which this 
            GenericObject is located
        """
        
        lParent = self.getParent()
        
        while not lParent.isInstanceOf("Environment"):
            lParent = lParent.getParent()
        
        return lParent
    
    #---------------------------------------------------------------#

    def getMessaging(self):
        """
            Return a reference to the Messaging System for this
            Environment
        """
        
        return self.getEnvironment().getMessaging()
    
    #---------------------------------------------------------------#

    def subscribe(self, name):
        """
            Subscribe this object with the environment messaging
            system for the specified filter/group/topic name
        """
        
        self.getMessaging().subscribeFor(name, self)

    #---------------------------------------------------------------#

    def unsubscribe(self, name):
        """
            Unsubscribe this object from the environment messaging
            system for the specified filter/group/topic name
        """
    
        self.getMessaging().unsubscribeFor(name, self)
        
    #---------------------------------------------------------------#

    def publish(self, message):
        """
            Publish the specified message with the environment
            messaging system
        """

        #publish the message        
        self.getMessaging().publishMessage(message)       
        
    #---------------------------------------------------------------#

    def getNrProperties(self):
        """
            Return the number of properties defined
            by the class implementing this interface
        """
        
        return self.getClassInfo().getNrProperties()
        
    #---------------------------------------------------------------#
        
    def isPropertyDefined(self, propertyIdentity):
        """
            Return if the specified property is defined 
            by the class implementing this interface. 
        """

        return self.getClassInfo().isPropertyDefined(propertyIdentity)
        
    #---------------------------------------------------------------#
        
    def getPropertyDefinition(self, propertyIdentity):
        """
            Return the PropertyDefinition for the specified
            property identity. Returns None if the proeprty definition
            is unknown

            RETURN TYPE: PropertyDefinition
        """

        return self.getClassInfo().getPropertyDefinition(propertyIdentity)
        
    #---------------------------------------------------------------#
        
    def getPropertyDefinitions(self):
        """
            Return the PropertyDefinitions for the
            class implementing this interface

            RETURN TYPE: [PropertyDefinition]
        """

        return self.getClassInfo().getPropertyDefinitions()
        
    #---------------------------------------------------------------#
        
    def addPropertyDefinition(self, propertyDefinition):
        """
            Adds/overrides the specified property definition to/in
            the class implementing this interface
        """

        self.getClassInfo().addPropertyDefinition(propertyDefinition)
        
    #---------------------------------------------------------------#
        
    def removePropertyDefinition(self, propertyIdentity):
        """
            Removes the specified property definition
            from the class implementing this interface
        """

        self.getClassInfo().removePropertyDefinition(propertyIdentity)
        
    #---------------------------------------------------------------#

    def hasEditableConstructorProperties(self):
        """
            Returns if the class implementing this
            interface has editable constructor properties
        """

        return self.getClassInfo().hasEditableConstructorProperties()
        
    #---------------------------------------------------------------#

    def hasEditableProperties(self):
        """
            Returns if the class implementing this
            interface has editable properties
        """

        return self.getClassInfo().hasEditableProperties()

    #---------------------------------------------------------------#

    def getZopeMetaTypes(self):
        """
            ABSTRACT: Returns specialized Zope all_meta_types tuple
            for the implementing class.
            
            NOTE: override this method to provide specialized
            all_meta_types tuple the implementing class
        
            NOTE: You should probably include the super class result 
            of this method when overriding.
        """
        
        #the default is no specialized Meta Types.
        return ()
        
    #---------------------------------------------------------------#

    def manage_PropertyEditing(self, REQUEST):
        """
            FINAL: This purpose of this method is to handle the property
            changes made using the property editor (propertyEditor.dtml)
            and posted to this method.
            
            This method handles the POST response from the 
            propertyEditor.dtml file.
            
            It is the responsibility of each property editlet 
            generated for this object to handle
            it's own property changes.
        """

        #determine all of the editlets for properties
        #of the object, and have them work out and set their
        #appropriate property values using the information contained
        #in the posted REQUEST.
        for propertyDefinition in self.getPropertyDefinitions():
        
            #does the current property support writing (updating)
            if propertyDefinition.isWritePermitted():
            
                #can we read the current value?
                if propertyDefinition.isReadPermitted():
                    #get the property editlet (using the current value of the property)
                    editlet = propertyDefinition.getEditlet(self, self.getPropertyValue(propertyDefinition.getIdentity()))
                
                else:
                    #get the property editlet (without using the current value of the property)
                    editlet = propertyDefinition.getEditlet(self)
                
                #does the property have an editlet
                if editlet != None:
                    #have the editlet set the new value from the request
                    editlet.updatePropertyWith(propertyDefinition, self, REQUEST)
        
        #the http response from saving the properties is the 
        #same page the user was visiting (with the saved message)
        return self.propertyEditor(self, REQUEST, manage_tabs_message="Changes Saved")

    #---------------------------------------------------------------#

    def manage_ObjectConstruction(self, REQUEST=None, COMMAND=None, CLASSNAME=None, PROPERTIES=None):
        """
            FINAL: This purpose of this method is to handle when
            an object is constructed using the property editor 
            (propertyEditor.dtml)
            
            This method handles the following scenarios;
            
            1. A GET request to create and instance of a registered
               Knowledge Engine class that requires a 
               constructor property editor.  In this case, this method
               will response with a property editor tailored for 
               the requested class using the dtml/propertyEditor.dtml file.
               
            2. A GET request to create and instance of a registered
               Knowledge Engine class that DOES NOT require a 
               constructor property editor. That is, all properties
               have default values that should not be edited by the
               end-user when creating and instance!  In this case, 
               this method will create a default instance of the
               requested class and add it as a child to this class.
            
            3. A POST request containing the property values from
               a constructor property editor for a particular
               registered Knowledge Engine class. (the POST request
               will originate from the dtml/propertyEditor.dtml)            
               In this case, this method will create an instance of the
               requested class, set the properties of the instance
               according to those defined in the request 
               and add it as a child to this class.            

            4. A program call that passes a PROPERTIES dictionary with the keid and
               other necessary properties. Because no REQUEST is passed to this
               object, it does not attempt a RESPONSE redirect but returns
               the newly created object

        """        

        #get the request command (either: CREATE_CONSTRUCTOR or CREATE_INSTANCE)
        #CREATE_CONSTRUCTOR is used to request that a constructor be returned
        #CREATE_INSTANCE is used to request that an instance be created using the provided request

        if PROPERTIES == None:
            PROPERTIES = REQUEST

        if COMMAND == None:
            command = PROPERTIES['COMMAND']
        else:
            command = COMMAND
            
        #get the classinfo name from the request
        if CLASSNAME == None:
            className = PROPERTIES['CLASSNAME']
        else:
            className = CLASSNAME
        
        #get the registered classinfo instance from the core for the specified class
        classInfo = getClassInfo(className)
            
        #determine if the class supports constructor properties
        classSupportsConstructorProperties = classInfo.hasEditableConstructorProperties()
            
        #create a new identity for the object
        identity = generateIdentity(classInfo.getIdentityType())

        #is this request to create a constructor form for the specified class
        #(to get initial object properties from the user?)
        if classSupportsConstructorProperties and command == 'CREATE_CONSTRUCTOR':
            
            #yes... create a constructor property editor for the class
            return self.propertyEditor(REQUEST=PROPERTIES, ISCONSTRUCTOR=true, CLASSINFO=classInfo)
            
        else:
            #the request is not for an initial constructor!
            #(it is either to handle the properties from a constructor property editor OR
            # it is for an object that does not require a constructor property editor)
              
            #------------------
            #determine the proposed id for the instance
            
            #does the class support constructor properties?
            #(the request will contain the id)
            if classSupportsConstructorProperties:
                #get the id from the request
                id = PROPERTIES['keid']

            else:
                #the class doesn't use constructor property editing so
                #use the property reflection interface to retrieve the default id!
                id = classInfo.getPropertyDefinition('id').getDefaultValue()
                           
            #------------------
            #create the new instance using the class info for the desired instance type
            #(by doing this the classInfo object has the ability to control how an
            # instance is really initialised)
            instance = classInfo.newInstance(id, identity, PROPERTIES)

            #------------------
            #set the instance property values from the constructor property editor editlets                 
            if classSupportsConstructorProperties:
                #determine all of the editlets for properties
                #for the new instance and have them work out and set their
                #appropriate property values using the information contained
                #in the posted REQUEST.
                for propertyDefinition in instance.getPropertyDefinitions():

                    #does the current property support writing (updating)
                    if propertyDefinition.isWritePermitted():

                        #get the property constructor editlet (without using the current value of the property)
                        editlet = propertyDefinition.getEditlet(instance, isForConstructor=true)

                        #does the property have an editlet
                        if editlet != None:
                            #have the editlet set the new value from the request
                            editlet.updatePropertyWith(propertyDefinition, instance, PROPERTIES)

            #------------------
            #ensure that all non-edited properties are set to their defaults
            for propertyDefinition in instance.getPropertyDefinitions():

                #does the instance have the current property?
                if not hasattr(instance, propertyDefinition.getIdentity()):

                    #value is missing for the property, so add the default value
                    setattr(instance, propertyDefinition.getIdentity(), propertyDefinition.getDefaultValue())

            #------------------
            #add the instance to the container (self)
            self._setObject(instance.getId(), instance)

            #------------------
            #set the position of the instance in the container (if they are ordered)
            #(we have to get the object from the contain so it get's wrapped correctly)
            self._getOb(instance.getId()).setAsLast()
            
            #------------------
            #redirect to the new object (if possible)  
            if REQUEST is not None and hasattr(REQUEST, 'URL1'):
                try: 
                    url = self.DestinationURL()
                except: 
                    url = REQUEST['URL1']        
                REQUEST.RESPONSE.redirect(url + '/manage_main')          
            else:
                return self._getOb(instance.getId())

    #---------------------------------------------------------------#

    def setAsLast(self):
        """
            Set this component to be the last in the container
            iff it is orderable and the container is ordered
        """

        #get the parent
        parent = self.getParent()

        #set the position of this component in the container (if ordered)
        if self.isOrderable() and parent.isOrderedContainer():            

            #get an ordered list of siblings (without this sibling)
            orderedSiblings = [sibling for sibling in parent.getChildrenByOrder() if sibling != self]

            #set the order of the current siblings
            nextOrder = 1.0
            for sibling in orderedSiblings:
                sibling.setOrder(nextOrder)    
                nextOrder = nextOrder + 1.0

            #now set the current to be the last
            self.setOrder(nextOrder)       

    #---------------------------------------------------------------#

    def getOrder(self):
        """
            Return the relative position of the object in
            it's container.  Return None if the object can't be ordered.
            
            PRE: self.isOrderable()
        """
        
        if self.isOrderable():
            return self.order
        else:
            return None

    #---------------------------------------------------------------#

    def setOrder(self, order):
        """
            Sets the Order of the component in it's container

            PRE: self.isOrderable()
        """        
        
        if self.isOrderable():        
            self.order = order        
            
    #---------------------------------------------------------------#

    def isOrderable(self):
        """
            Return if the object can be ordered in an ordered 
            container.  Typically objects with 'order' attributes
            between -1000 and 1000 can be ordered.
        """
        
        return hasattr(self, 'order') and self.order > -1000 and self.order < 1000
        
    #---------------------------------------------------------------#

    def getPreviousSibling(self):
        """
            Return the sibling above this object.            
            Returns None if there is no previous sibling or this
            is the first sibling in the container
            
            PRE: self.isOrderable() and 
                 self.getParent() is a GenericContainer and
                 self.getParent().isOrderedContainer()
        """
    
        #ensure that the object is orderable and it is in an ordered container
        if self.isOrderable() and self.getParent().isOrderedContainer():
            
            #get the ordered children that occur before this object
            previousSiblings = [sibling for sibling 
                                in self.getParent().getChildren() 
                                if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() < self.getOrder()]
            
            #sort the previous siblings (in descending order so the previous sibling is first)
            previousSiblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))            

            #the previous sibling is the first in the list
            if len(previousSiblings) == 0:            
                return None
            else:   
                return previousSiblings[0]
        else:
            return None
        
    #---------------------------------------------------------------#

    def hasPreviousSibling(self):
        """
            Return if the object has a previous sibling.
        """

        return self.getPreviousSibling() != None
        
    #---------------------------------------------------------------#

    def getNextSibling(self):
        """
            Return the sibling below this object.            
            Returns None if there is no next sibling or this
            is the last sibling in the container
            
            PRE: self.isOrderable() and 
                 self.getParent() is a GenericContainer and
                 self.getParent().isOrderedContainer()
        """
    
        #ensure that the object is orderable and it is in an ordered container
        if self.isOrderable() and self.getParent().isOrderedContainer():
            
            #get the ordered children that occur after this object
            nextSiblings = [sibling for sibling 
                            in self.getParent().getChildren() 
                            if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() > self.getOrder()]
            
            #sort the previous siblings (in non-descending order so the next sibling is first)
            nextSiblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))            

            #the next sibling is the first in the list
            if len(nextSiblings) == 0:            
                return None
            else:   
                return nextSiblings[0]
        else:
            return None
        
    #---------------------------------------------------------------#

    def hasNextSibling(self):
        """
            Return if the object has a next sibling.
        """
                    
        return self.getNextSibling() != None
        
    #---------------------------------------------------------------#        

    def getUnhandledEventDefinitions(self):
        """
            Return a sorted collection of EventDefinitions that are 
            currently unhandled by the object.  Unhandled in this 
            circumstance means that the end-user hasn't added an event 
            handler for an event.
        """

        #get the immediate children of the container
        children = self.objectValues()
        
        #filter the children by event (we want a list of handled events)
        handledEvents = [e.getIdentity() for e in children if hasattr(e, "isInstanceOf") and e.isInstanceOf("Event")]
        
        #return a list of events that are not handled!
        return [e for e in self.getClassInfo().getEventDefinitions() if not e.getIdentity() in handledEvents]

    #---------------------------------------------------------------#

    def getHandledEvents(self):
        """
            Returns a sequence of (id, Event) that are 
            handled by this object
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the Event(s) only
        return [(x.getId(), x) for x in children if hasattr(x, "isInstanceOf") and x.isInstanceOf("Event")]

    #---------------------------------------------------------------#

    def getHandledEvent(self, eventIdentity):
        """
            Returns the specified handled event for this object.
            If the event isn't handled, None is returned.
            If the event is disabled, the event is still returned.
        """ 

        #find the event in the child collection
        events = [e for e in self.objectValues() if hasattr(e, "isInstanceOf") and e.isInstanceOf("Event") and e.getIdentity() == eventIdentity]
        
         #does the handled event exist?
        if len(events) == 1:
            return events[0]
        else:
            return None

    #---------------------------------------------------------------#

    def isEventEnabled(self, eventIdentity):
        """
            Returns the specified event is enabled (and handled) by 
            this object (that is, it is defined and enabled)
        """
    
        #get the handled event
        event = self.getHandledEvent(eventIdentity)
        
        #is the event enabled?
        return event != None and event.isEnabled()

    #---------------------------------------------------------------#

    def getEnabledEvent(self, eventIdentity):
        """
            Returns the specified handled event for this object.
            If the event isn't handled, None is returned.
            If the event is disabled, None is returned.
        """
    
        #get the handled event
        event = self.getHandledEvent(eventIdentity)
        
        #is the event enabled?
        if event.isEnabled():
            return event
        else:
            return None        
        
    #---------------------------------------------------------------#

    def manage_EventConstruction(self, REQUEST=None, EVENT=None):
        """
            FINAL: This purpose of this method is to handle when
            an event is constructed using the events tab
            
            This method handles the following scenarios;
            
            1. A GET request to create an instance of an event, with
               the specified identity.  In this case, this method
               will respond with a management interface tailored for 
               the requested event.
        """        
            
        #get the proposed identity of the event from the request
        if EVENT == None:
            identity = REQUEST['EVENT']                
        else:
            identity = EVENT
        
        #------------------
        #get the event definition 
        eventDefinition = self.getClassInfo().getEventDefinition(identity)
                
        #create an event with the specified identity
        #(also sets the zopeid to be the identity)
        classInfo = getClassInfo('Event')
        
        #------------------
        #create the new instance using the class info for the desired instance type
        #(by doing this the classInfo object has the ability to control how an
        # instance is really initialised)
        event = classInfo.newInstance(eventDefinition.getIdentity(), eventDefinition.getIdentity(), REQUEST)

        #set the description of the event
        event.setDescription(eventDefinition.getDescription())
        
        #set that the event is enabled
        event.setIsEnabled(true)

        #------------------
        #add the instance to the container (self)
        self._setObject(eventDefinition.getIdentity(), event)

        #------------------
        #redirect to the new object (if possible)  
        if REQUEST is not None and hasattr(REQUEST, 'URL1'):
            try: 
                url = self.DestinationURL()
            except: 
                url = REQUEST['URL1']        
            REQUEST.RESPONSE.redirect(url + '/eventEditor')          

    #---------------------------------------------------------------#        

#####################################################################
## Class Initialisation
# (none)
